using System;
using ValidationFramework.Extensions;

namespace ValidationFramework.Specifications
{
	/// <summary>
	/// Base class for performing a range validation.
	/// </summary>
#if (!SILVERLIGHT)
	[Serializable]
#endif
    public class IsInRangeSpec<T> : SpecificationBase, IIsInRangeSpecification
	{
		// --- Fields

		private const string errorMessageFormat = "The {0} '{1}' must be '{2}' {3} and '{4}' {5}.";
		private const string ruleInterpretationFormat = "The value must be '{0}' {1} and '{2}' {3}.";


		// --- Constructors

		/// <param name="minimum">The minimum valid value</param>
		/// <param name="maximum">The maximum valid value</param>
		public IsInRangeSpec(T minimum, T maximum)
		{
			Minimum = minimum;
			Maximum = maximum;
			EqualsMinimumIsValid = true;
			EqualsMaximumIsValid = true;
		}


		// --- Properties

		/// <summary>
		/// Gets the minimum valid value
		/// </summary>
		public T Minimum
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets the maximum valid value
		/// </summary>
		public T Maximum
		{
			get;
			private set;
		}

		/// <inheritdoc />
		public override string ToString()
		{
			CompareOperator minCompareOperator;
			CompareOperator maxCompareOperator;
			GetCompareOperators(out maxCompareOperator, out minCompareOperator);
			return string.Format(ruleInterpretationFormat, minCompareOperator.ToUserFriendlyString(), Minimum, maxCompareOperator.ToUserFriendlyString(), Maximum);
		}

		/// <summary>
		/// Get or sets value indicating if the minimum value is valid.
		/// </summary>
		public bool EqualsMinimumIsValid
		{
			get;
			set;
		}

		/// <summary>
		/// Get or sets a value indicating if the maximum value is valid.
		/// </summary>
		public bool EqualsMaximumIsValid
		{
			get;
			set;
		}


		// --- Methods

        public override bool CanBeSatisfiedBy(Type typeToTest)
        {
            return base.CanBeSatisfiedBy(typeToTest) && typeof(IComparable).IsAssignableFrom(typeToTest);
        }
		/// <inheritdoc />
		public override string DefaultMessage(string tokenizedMemberName, string descriptorType)
		{
			CompareOperator minCompareOperator;
			CompareOperator maxCompareOperator;
			GetCompareOperators(out maxCompareOperator, out minCompareOperator);
			return string.Format(errorMessageFormat, descriptorType, tokenizedMemberName, minCompareOperator.ToUserFriendlyString(), Minimum, maxCompareOperator.ToUserFriendlyString(), Maximum);
		}

		/// <inheritdoc />
		public override bool IsSatisfiedBy(object targetValue)
		{
			if (targetValue == null)
				return true;

			bool isAboveMin;
			if (EqualsMinimumIsValid)
			{
				isAboveMin = ComparisonHelper.Compare(targetValue, Minimum, CompareOperator.GreaterThanEqual);
			}
			else
			{
				isAboveMin = ComparisonHelper.Compare(targetValue, Minimum, CompareOperator.GreaterThan);
			}

			if (isAboveMin)
			{
				bool isBelowMax;
				if (EqualsMaximumIsValid)
				{
					isBelowMax = ComparisonHelper.Compare(targetValue, Maximum, CompareOperator.LessThanEqual);
				}
				else
				{
					isBelowMax = ComparisonHelper.Compare(targetValue, Maximum, CompareOperator.LessThan);
				}

				return (isBelowMax);
			}
			return false;
		}

		/// <inheritdoc />
		public override bool IsEquivalentTo(ISpecification spec)
		{
			var rangeSpecification = (IsInRangeSpec<T>)spec;
			return (rangeSpecification.EqualsMaximumIsValid == EqualsMaximumIsValid) &&
				   (rangeSpecification.EqualsMinimumIsValid == EqualsMinimumIsValid) &&
				   CompareT(Minimum, rangeSpecification.Minimum) &&
				   CompareT(Maximum, rangeSpecification.Maximum);
		}


		// --- Methods | Helpers

		private void GetCompareOperators(out CompareOperator maxCompareOperator, out CompareOperator minCompareOperator)
		{
			if (EqualsMaximumIsValid)
			{
				maxCompareOperator = CompareOperator.LessThanEqual;
			}
			else
			{
				maxCompareOperator = CompareOperator.LessThan;
			}
			if (EqualsMinimumIsValid)
			{
				minCompareOperator = CompareOperator.GreaterThanEqual;
			}
			else
			{
				minCompareOperator = CompareOperator.GreaterThan;
			}
		}

		private static bool CompareT(object left, object right)
		{
			if (left == null && right != null)
			{
				return false;
			}
			else if (left != null && right == null)
			{
				return false;
			}
			else if (left == null && right == null)
			{
				return true;
			}
			else
			{
				return left.Equals(right);
			}
		}

	}
}
